// @ts-nocheck
// 源于piexifjs
import { cloneDeep } from '../cloneDeep'
import { isString } from '../isString'
const TAGS = {
	'Image': {
		11: {
			'name': 'ProcessingSoftware',
			'type': 'Ascii'
		},
		254: {
			'name': 'NewSubfileType',
			'type': 'Long'
		},
		255: {
			'name': 'SubfileType',
			'type': 'Short'
		},
		256: {
			'name': 'ImageWidth',
			'type': 'Long'
		},
		257: {
			'name': 'ImageLength',
			'type': 'Long'
		},
		258: {
			'name': 'BitsPerSample',
			'type': 'Short'
		},
		259: {
			'name': 'Compression',
			'type': 'Short'
		},
		262: {
			'name': 'PhotometricInterpretation',
			'type': 'Short'
		},
		263: {
			'name': 'Threshholding',
			'type': 'Short'
		},
		264: {
			'name': 'CellWidth',
			'type': 'Short'
		},
		265: {
			'name': 'CellLength',
			'type': 'Short'
		},
		266: {
			'name': 'FillOrder',
			'type': 'Short'
		},
		269: {
			'name': 'DocumentName',
			'type': 'Ascii'
		},
		270: {
			'name': 'ImageDescription',
			'type': 'Ascii'
		},
		271: {
			'name': 'Make',
			'type': 'Ascii'
		},
		272: {
			'name': 'Model',
			'type': 'Ascii'
		},
		273: {
			'name': 'StripOffsets',
			'type': 'Long'
		},
		274: {
			'name': 'Orientation',
			'type': 'Short'
		},
		277: {
			'name': 'SamplesPerPixel',
			'type': 'Short'
		},
		278: {
			'name': 'RowsPerStrip',
			'type': 'Long'
		},
		279: {
			'name': 'StripByteCounts',
			'type': 'Long'
		},
		282: {
			'name': 'XResolution',
			'type': 'Rational'
		},
		283: {
			'name': 'YResolution',
			'type': 'Rational'
		},
		284: {
			'name': 'PlanarConfiguration',
			'type': 'Short'
		},
		290: {
			'name': 'GrayResponseUnit',
			'type': 'Short'
		},
		291: {
			'name': 'GrayResponseCurve',
			'type': 'Short'
		},
		292: {
			'name': 'T4Options',
			'type': 'Long'
		},
		293: {
			'name': 'T6Options',
			'type': 'Long'
		},
		296: {
			'name': 'ResolutionUnit',
			'type': 'Short'
		},
		301: {
			'name': 'TransferFunction',
			'type': 'Short'
		},
		305: {
			'name': 'Software',
			'type': 'Ascii'
		},
		306: {
			'name': 'DateTime',
			'type': 'Ascii'
		},
		315: {
			'name': 'Artist',
			'type': 'Ascii'
		},
		316: {
			'name': 'HostComputer',
			'type': 'Ascii'
		},
		317: {
			'name': 'Predictor',
			'type': 'Short'
		},
		318: {
			'name': 'WhitePoint',
			'type': 'Rational'
		},
		319: {
			'name': 'PrimaryChromaticities',
			'type': 'Rational'
		},
		320: {
			'name': 'ColorMap',
			'type': 'Short'
		},
		321: {
			'name': 'HalftoneHints',
			'type': 'Short'
		},
		322: {
			'name': 'TileWidth',
			'type': 'Short'
		},
		323: {
			'name': 'TileLength',
			'type': 'Short'
		},
		324: {
			'name': 'TileOffsets',
			'type': 'Short'
		},
		325: {
			'name': 'TileByteCounts',
			'type': 'Short'
		},
		330: {
			'name': 'SubIFDs',
			'type': 'Long'
		},
		332: {
			'name': 'InkSet',
			'type': 'Short'
		},
		333: {
			'name': 'InkNames',
			'type': 'Ascii'
		},
		334: {
			'name': 'NumberOfInks',
			'type': 'Short'
		},
		336: {
			'name': 'DotRange',
			'type': 'Byte'
		},
		337: {
			'name': 'TargetPrinter',
			'type': 'Ascii'
		},
		338: {
			'name': 'ExtraSamples',
			'type': 'Short'
		},
		339: {
			'name': 'SampleFormat',
			'type': 'Short'
		},
		340: {
			'name': 'SMinSampleValue',
			'type': 'Short'
		},
		341: {
			'name': 'SMaxSampleValue',
			'type': 'Short'
		},
		342: {
			'name': 'TransferRange',
			'type': 'Short'
		},
		343: {
			'name': 'ClipPath',
			'type': 'Byte'
		},
		344: {
			'name': 'XClipPathUnits',
			'type': 'Long'
		},
		345: {
			'name': 'YClipPathUnits',
			'type': 'Long'
		},
		346: {
			'name': 'Indexed',
			'type': 'Short'
		},
		347: {
			'name': 'JPEGTables',
			'type': 'Undefined'
		},
		351: {
			'name': 'OPIProxy',
			'type': 'Short'
		},
		512: {
			'name': 'JPEGProc',
			'type': 'Long'
		},
		513: {
			'name': 'JPEGInterchangeFormat',
			'type': 'Long'
		},
		514: {
			'name': 'JPEGInterchangeFormatLength',
			'type': 'Long'
		},
		515: {
			'name': 'JPEGRestartInterval',
			'type': 'Short'
		},
		517: {
			'name': 'JPEGLosslessPredictors',
			'type': 'Short'
		},
		518: {
			'name': 'JPEGPointTransforms',
			'type': 'Short'
		},
		519: {
			'name': 'JPEGQTables',
			'type': 'Long'
		},
		520: {
			'name': 'JPEGDCTables',
			'type': 'Long'
		},
		521: {
			'name': 'JPEGACTables',
			'type': 'Long'
		},
		529: {
			'name': 'YCbCrCoefficients',
			'type': 'Rational'
		},
		530: {
			'name': 'YCbCrSubSampling',
			'type': 'Short'
		},
		531: {
			'name': 'YCbCrPositioning',
			'type': 'Short'
		},
		532: {
			'name': 'ReferenceBlackWhite',
			'type': 'Rational'
		},
		700: {
			'name': 'XMLPacket',
			'type': 'Byte'
		},
		18246: {
			'name': 'Rating',
			'type': 'Short'
		},
		18249: {
			'name': 'RatingPercent',
			'type': 'Short'
		},
		32781: {
			'name': 'ImageID',
			'type': 'Ascii'
		},
		33421: {
			'name': 'CFARepeatPatternDim',
			'type': 'Short'
		},
		33422: {
			'name': 'CFAPattern',
			'type': 'Byte'
		},
		33423: {
			'name': 'BatteryLevel',
			'type': 'Rational'
		},
		33432: {
			'name': 'Copyright',
			'type': 'Ascii'
		},
		33434: {
			'name': 'ExposureTime',
			'type': 'Rational'
		},
		34377: {
			'name': 'ImageResources',
			'type': 'Byte'
		},
		34665: {
			'name': 'ExifTag',
			'type': 'Long'
		},
		34675: {
			'name': 'InterColorProfile',
			'type': 'Undefined'
		},
		34853: {
			'name': 'GPSTag',
			'type': 'Long'
		},
		34857: {
			'name': 'Interlace',
			'type': 'Short'
		},
		34858: {
			'name': 'TimeZoneOffset',
			'type': 'Long'
		},
		34859: {
			'name': 'SelfTimerMode',
			'type': 'Short'
		},
		37387: {
			'name': 'FlashEnergy',
			'type': 'Rational'
		},
		37388: {
			'name': 'SpatialFrequencyResponse',
			'type': 'Undefined'
		},
		37389: {
			'name': 'Noise',
			'type': 'Undefined'
		},
		37390: {
			'name': 'FocalPlaneXResolution',
			'type': 'Rational'
		},
		37391: {
			'name': 'FocalPlaneYResolution',
			'type': 'Rational'
		},
		37392: {
			'name': 'FocalPlaneResolutionUnit',
			'type': 'Short'
		},
		37393: {
			'name': 'ImageNumber',
			'type': 'Long'
		},
		37394: {
			'name': 'SecurityClassification',
			'type': 'Ascii'
		},
		37395: {
			'name': 'ImageHistory',
			'type': 'Ascii'
		},
		37397: {
			'name': 'ExposureIndex',
			'type': 'Rational'
		},
		37398: {
			'name': 'TIFFEPStandardID',
			'type': 'Byte'
		},
		37399: {
			'name': 'SensingMethod',
			'type': 'Short'
		},
		40091: {
			'name': 'XPTitle',
			'type': 'Byte'
		},
		40092: {
			'name': 'XPComment',
			'type': 'Byte'
		},
		40093: {
			'name': 'XPAuthor',
			'type': 'Byte'
		},
		40094: {
			'name': 'XPKeywords',
			'type': 'Byte'
		},
		40095: {
			'name': 'XPSubject',
			'type': 'Byte'
		},
		50341: {
			'name': 'PrintImageMatching',
			'type': 'Undefined'
		},
		50706: {
			'name': 'DNGVersion',
			'type': 'Byte'
		},
		50707: {
			'name': 'DNGBackwardVersion',
			'type': 'Byte'
		},
		50708: {
			'name': 'UniqueCameraModel',
			'type': 'Ascii'
		},
		50709: {
			'name': 'LocalizedCameraModel',
			'type': 'Byte'
		},
		50710: {
			'name': 'CFAPlaneColor',
			'type': 'Byte'
		},
		50711: {
			'name': 'CFALayout',
			'type': 'Short'
		},
		50712: {
			'name': 'LinearizationTable',
			'type': 'Short'
		},
		50713: {
			'name': 'BlackLevelRepeatDim',
			'type': 'Short'
		},
		50714: {
			'name': 'BlackLevel',
			'type': 'Rational'
		},
		50715: {
			'name': 'BlackLevelDeltaH',
			'type': 'SRational'
		},
		50716: {
			'name': 'BlackLevelDeltaV',
			'type': 'SRational'
		},
		50717: {
			'name': 'WhiteLevel',
			'type': 'Short'
		},
		50718: {
			'name': 'DefaultScale',
			'type': 'Rational'
		},
		50719: {
			'name': 'DefaultCropOrigin',
			'type': 'Short'
		},
		50720: {
			'name': 'DefaultCropSize',
			'type': 'Short'
		},
		50721: {
			'name': 'ColorMatrix1',
			'type': 'SRational'
		},
		50722: {
			'name': 'ColorMatrix2',
			'type': 'SRational'
		},
		50723: {
			'name': 'CameraCalibration1',
			'type': 'SRational'
		},
		50724: {
			'name': 'CameraCalibration2',
			'type': 'SRational'
		},
		50725: {
			'name': 'ReductionMatrix1',
			'type': 'SRational'
		},
		50726: {
			'name': 'ReductionMatrix2',
			'type': 'SRational'
		},
		50727: {
			'name': 'AnalogBalance',
			'type': 'Rational'
		},
		50728: {
			'name': 'AsShotNeutral',
			'type': 'Short'
		},
		50729: {
			'name': 'AsShotWhiteXY',
			'type': 'Rational'
		},
		50730: {
			'name': 'BaselineExposure',
			'type': 'SRational'
		},
		50731: {
			'name': 'BaselineNoise',
			'type': 'Rational'
		},
		50732: {
			'name': 'BaselineSharpness',
			'type': 'Rational'
		},
		50733: {
			'name': 'BayerGreenSplit',
			'type': 'Long'
		},
		50734: {
			'name': 'LinearResponseLimit',
			'type': 'Rational'
		},
		50735: {
			'name': 'CameraSerialNumber',
			'type': 'Ascii'
		},
		50736: {
			'name': 'LensInfo',
			'type': 'Rational'
		},
		50737: {
			'name': 'ChromaBlurRadius',
			'type': 'Rational'
		},
		50738: {
			'name': 'AntiAliasStrength',
			'type': 'Rational'
		},
		50739: {
			'name': 'ShadowScale',
			'type': 'SRational'
		},
		50740: {
			'name': 'DNGPrivateData',
			'type': 'Byte'
		},
		50741: {
			'name': 'MakerNoteSafety',
			'type': 'Short'
		},
		50778: {
			'name': 'CalibrationIlluminant1',
			'type': 'Short'
		},
		50779: {
			'name': 'CalibrationIlluminant2',
			'type': 'Short'
		},
		50780: {
			'name': 'BestQualityScale',
			'type': 'Rational'
		},
		50781: {
			'name': 'RawDataUniqueID',
			'type': 'Byte'
		},
		50827: {
			'name': 'OriginalRawFileName',
			'type': 'Byte'
		},
		50828: {
			'name': 'OriginalRawFileData',
			'type': 'Undefined'
		},
		50829: {
			'name': 'ActiveArea',
			'type': 'Short'
		},
		50830: {
			'name': 'MaskedAreas',
			'type': 'Short'
		},
		50831: {
			'name': 'AsShotICCProfile',
			'type': 'Undefined'
		},
		50832: {
			'name': 'AsShotPreProfileMatrix',
			'type': 'SRational'
		},
		50833: {
			'name': 'CurrentICCProfile',
			'type': 'Undefined'
		},
		50834: {
			'name': 'CurrentPreProfileMatrix',
			'type': 'SRational'
		},
		50879: {
			'name': 'ColorimetricReference',
			'type': 'Short'
		},
		50931: {
			'name': 'CameraCalibrationSignature',
			'type': 'Byte'
		},
		50932: {
			'name': 'ProfileCalibrationSignature',
			'type': 'Byte'
		},
		50934: {
			'name': 'AsShotProfileName',
			'type': 'Byte'
		},
		50935: {
			'name': 'NoiseReductionApplied',
			'type': 'Rational'
		},
		50936: {
			'name': 'ProfileName',
			'type': 'Byte'
		},
		50937: {
			'name': 'ProfileHueSatMapDims',
			'type': 'Long'
		},
		50938: {
			'name': 'ProfileHueSatMapData1',
			'type': 'Float'
		},
		50939: {
			'name': 'ProfileHueSatMapData2',
			'type': 'Float'
		},
		50940: {
			'name': 'ProfileToneCurve',
			'type': 'Float'
		},
		50941: {
			'name': 'ProfileEmbedPolicy',
			'type': 'Long'
		},
		50942: {
			'name': 'ProfileCopyright',
			'type': 'Byte'
		},
		50964: {
			'name': 'ForwardMatrix1',
			'type': 'SRational'
		},
		50965: {
			'name': 'ForwardMatrix2',
			'type': 'SRational'
		},
		50966: {
			'name': 'PreviewApplicationName',
			'type': 'Byte'
		},
		50967: {
			'name': 'PreviewApplicationVersion',
			'type': 'Byte'
		},
		50968: {
			'name': 'PreviewSettingsName',
			'type': 'Byte'
		},
		50969: {
			'name': 'PreviewSettingsDigest',
			'type': 'Byte'
		},
		50970: {
			'name': 'PreviewColorSpace',
			'type': 'Long'
		},
		50971: {
			'name': 'PreviewDateTime',
			'type': 'Ascii'
		},
		50972: {
			'name': 'RawImageDigest',
			'type': 'Undefined'
		},
		50973: {
			'name': 'OriginalRawFileDigest',
			'type': 'Undefined'
		},
		50974: {
			'name': 'SubTileBlockSize',
			'type': 'Long'
		},
		50975: {
			'name': 'RowInterleaveFactor',
			'type': 'Long'
		},
		50981: {
			'name': 'ProfileLookTableDims',
			'type': 'Long'
		},
		50982: {
			'name': 'ProfileLookTableData',
			'type': 'Float'
		},
		51008: {
			'name': 'OpcodeList1',
			'type': 'Undefined'
		},
		51009: {
			'name': 'OpcodeList2',
			'type': 'Undefined'
		},
		51022: {
			'name': 'OpcodeList3',
			'type': 'Undefined'
		}
	},
	'Exif': {
		33434: {
			'name': 'ExposureTime',
			'type': 'Rational'
		},
		33437: {
			'name': 'FNumber',
			'type': 'Rational'
		},
		34850: {
			'name': 'ExposureProgram',
			'type': 'Short'
		},
		34852: {
			'name': 'SpectralSensitivity',
			'type': 'Ascii'
		},
		34855: {
			'name': 'ISOSpeedRatings',
			'type': 'Short'
		},
		34856: {
			'name': 'OECF',
			'type': 'Undefined'
		},
		34864: {
			'name': 'SensitivityType',
			'type': 'Short'
		},
		34865: {
			'name': 'StandardOutputSensitivity',
			'type': 'Long'
		},
		34866: {
			'name': 'RecommendedExposureIndex',
			'type': 'Long'
		},
		34867: {
			'name': 'ISOSpeed',
			'type': 'Long'
		},
		34868: {
			'name': 'ISOSpeedLatitudeyyy',
			'type': 'Long'
		},
		34869: {
			'name': 'ISOSpeedLatitudezzz',
			'type': 'Long'
		},
		36864: {
			'name': 'ExifVersion',
			'type': 'Undefined'
		},
		36867: {
			'name': 'DateTimeOriginal',
			'type': 'Ascii'
		},
		36868: {
			'name': 'DateTimeDigitized',
			'type': 'Ascii'
		},
		37121: {
			'name': 'ComponentsConfiguration',
			'type': 'Undefined'
		},
		37122: {
			'name': 'CompressedBitsPerPixel',
			'type': 'Rational'
		},
		37377: {
			'name': 'ShutterSpeedValue',
			'type': 'SRational'
		},
		37378: {
			'name': 'ApertureValue',
			'type': 'Rational'
		},
		37379: {
			'name': 'BrightnessValue',
			'type': 'SRational'
		},
		37380: {
			'name': 'ExposureBiasValue',
			'type': 'SRational'
		},
		37381: {
			'name': 'MaxApertureValue',
			'type': 'Rational'
		},
		37382: {
			'name': 'SubjectDistance',
			'type': 'Rational'
		},
		37383: {
			'name': 'MeteringMode',
			'type': 'Short'
		},
		37384: {
			'name': 'LightSource',
			'type': 'Short'
		},
		37385: {
			'name': 'Flash',
			'type': 'Short'
		},
		37386: {
			'name': 'FocalLength',
			'type': 'Rational'
		},
		37396: {
			'name': 'SubjectArea',
			'type': 'Short'
		},
		37500: {
			'name': 'MakerNote',
			'type': 'Undefined'
		},
		37510: {
			'name': 'UserComment',
			'type': 'Ascii'
		},
		37520: {
			'name': 'SubSecTime',
			'type': 'Ascii'
		},
		37521: {
			'name': 'SubSecTimeOriginal',
			'type': 'Ascii'
		},
		37522: {
			'name': 'SubSecTimeDigitized',
			'type': 'Ascii'
		},
		40960: {
			'name': 'FlashpixVersion',
			'type': 'Undefined'
		},
		40961: {
			'name': 'ColorSpace',
			'type': 'Short'
		},
		40962: {
			'name': 'PixelXDimension',
			'type': 'Long'
		},
		40963: {
			'name': 'PixelYDimension',
			'type': 'Long'
		},
		40964: {
			'name': 'RelatedSoundFile',
			'type': 'Ascii'
		},
		40965: {
			'name': 'InteroperabilityTag',
			'type': 'Long'
		},
		41483: {
			'name': 'FlashEnergy',
			'type': 'Rational'
		},
		41484: {
			'name': 'SpatialFrequencyResponse',
			'type': 'Undefined'
		},
		41486: {
			'name': 'FocalPlaneXResolution',
			'type': 'Rational'
		},
		41487: {
			'name': 'FocalPlaneYResolution',
			'type': 'Rational'
		},
		41488: {
			'name': 'FocalPlaneResolutionUnit',
			'type': 'Short'
		},
		41492: {
			'name': 'SubjectLocation',
			'type': 'Short'
		},
		41493: {
			'name': 'ExposureIndex',
			'type': 'Rational'
		},
		41495: {
			'name': 'SensingMethod',
			'type': 'Short'
		},
		41728: {
			'name': 'FileSource',
			'type': 'Undefined'
		},
		41729: {
			'name': 'SceneType',
			'type': 'Undefined'
		},
		41730: {
			'name': 'CFAPattern',
			'type': 'Undefined'
		},
		41985: {
			'name': 'CustomRendered',
			'type': 'Short'
		},
		41986: {
			'name': 'ExposureMode',
			'type': 'Short'
		},
		41987: {
			'name': 'WhiteBalance',
			'type': 'Short'
		},
		41988: {
			'name': 'DigitalZoomRatio',
			'type': 'Rational'
		},
		41989: {
			'name': 'FocalLengthIn35mmFilm',
			'type': 'Short'
		},
		41990: {
			'name': 'SceneCaptureType',
			'type': 'Short'
		},
		41991: {
			'name': 'GainControl',
			'type': 'Short'
		},
		41992: {
			'name': 'Contrast',
			'type': 'Short'
		},
		41993: {
			'name': 'Saturation',
			'type': 'Short'
		},
		41994: {
			'name': 'Sharpness',
			'type': 'Short'
		},
		41995: {
			'name': 'DeviceSettingDescription',
			'type': 'Undefined'
		},
		41996: {
			'name': 'SubjectDistanceRange',
			'type': 'Short'
		},
		42016: {
			'name': 'ImageUniqueID',
			'type': 'Ascii'
		},
		42032: {
			'name': 'CameraOwnerName',
			'type': 'Ascii'
		},
		42033: {
			'name': 'BodySerialNumber',
			'type': 'Ascii'
		},
		42034: {
			'name': 'LensSpecification',
			'type': 'Rational'
		},
		42035: {
			'name': 'LensMake',
			'type': 'Ascii'
		},
		42036: {
			'name': 'LensModel',
			'type': 'Ascii'
		},
		42037: {
			'name': 'LensSerialNumber',
			'type': 'Ascii'
		},
		42240: {
			'name': 'Gamma',
			'type': 'Rational'
		}
	},
	'GPS': {
		0: {
			'name': 'GPSVersionID',
			'type': 'Byte'
		},
		1: {
			'name': 'GPSLatitudeRef',
			'type': 'Ascii'
		},
		2: {
			'name': 'GPSLatitude',
			'type': 'Rational'
		},
		3: {
			'name': 'GPSLongitudeRef',
			'type': 'Ascii'
		},
		4: {
			'name': 'GPSLongitude',
			'type': 'Rational'
		},
		5: {
			'name': 'GPSAltitudeRef',
			'type': 'Byte'
		},
		6: {
			'name': 'GPSAltitude',
			'type': 'Rational'
		},
		7: {
			'name': 'GPSTimeStamp',
			'type': 'Rational'
		},
		8: {
			'name': 'GPSSatellites',
			'type': 'Ascii'
		},
		9: {
			'name': 'GPSStatus',
			'type': 'Ascii'
		},
		10: {
			'name': 'GPSMeasureMode',
			'type': 'Ascii'
		},
		11: {
			'name': 'GPSDOP',
			'type': 'Rational'
		},
		12: {
			'name': 'GPSSpeedRef',
			'type': 'Ascii'
		},
		13: {
			'name': 'GPSSpeed',
			'type': 'Rational'
		},
		14: {
			'name': 'GPSTrackRef',
			'type': 'Ascii'
		},
		15: {
			'name': 'GPSTrack',
			'type': 'Rational'
		},
		16: {
			'name': 'GPSImgDirectionRef',
			'type': 'Ascii'
		},
		17: {
			'name': 'GPSImgDirection',
			'type': 'Rational'
		},
		18: {
			'name': 'GPSMapDatum',
			'type': 'Ascii'
		},
		19: {
			'name': 'GPSDestLatitudeRef',
			'type': 'Ascii'
		},
		20: {
			'name': 'GPSDestLatitude',
			'type': 'Rational'
		},
		21: {
			'name': 'GPSDestLongitudeRef',
			'type': 'Ascii'
		},
		22: {
			'name': 'GPSDestLongitude',
			'type': 'Rational'
		},
		23: {
			'name': 'GPSDestBearingRef',
			'type': 'Ascii'
		},
		24: {
			'name': 'GPSDestBearing',
			'type': 'Rational'
		},
		25: {
			'name': 'GPSDestDistanceRef',
			'type': 'Ascii'
		},
		26: {
			'name': 'GPSDestDistance',
			'type': 'Rational'
		},
		27: {
			'name': 'GPSProcessingMethod',
			'type': 'Undefined'
		},
		28: {
			'name': 'GPSAreaInformation',
			'type': 'Undefined'
		},
		29: {
			'name': 'GPSDateStamp',
			'type': 'Ascii'
		},
		30: {
			'name': 'GPSDifferential',
			'type': 'Short'
		},
		31: {
			'name': 'GPSHPositioningError',
			'type': 'Rational'
		}
	},
	'Interop': {
		1: {
			'name': 'InteroperabilityIndex',
			'type': 'Ascii'
		}
	},
};
const TYPES = {
	"Byte": 1,
	"Ascii": 2,
	"Short": 3,
	"Long": 4,
	"Rational": 5,
	"Undefined": 7,
	"SLong": 9,
	"SRational": 10
};
TAGS["0th"] = TAGS["Image"];
TAGS["1st"] = TAGS["Image"];
class Piexif {
	version = "1.0.4"
	TAGS = TAGS
	ImageIFD = {
		ProcessingSoftware: 11,
		NewSubfileType: 254,
		SubfileType: 255,
		ImageWidth: 256,
		ImageLength: 257,
		BitsPerSample: 258,
		Compression: 259,
		PhotometricInterpretation: 262,
		Threshholding: 263,
		CellWidth: 264,
		CellLength: 265,
		FillOrder: 266,
		DocumentName: 269,
		ImageDescription: 270,
		Make: 271,
		Model: 272,
		StripOffsets: 273,
		Orientation: 274,
		SamplesPerPixel: 277,
		RowsPerStrip: 278,
		StripByteCounts: 279,
		XResolution: 282,
		YResolution: 283,
		PlanarConfiguration: 284,
		GrayResponseUnit: 290,
		GrayResponseCurve: 291,
		T4Options: 292,
		T6Options: 293,
		ResolutionUnit: 296,
		TransferFunction: 301,
		Software: 305,
		DateTime: 306,
		Artist: 315,
		HostComputer: 316,
		Predictor: 317,
		WhitePoint: 318,
		PrimaryChromaticities: 319,
		ColorMap: 320,
		HalftoneHints: 321,
		TileWidth: 322,
		TileLength: 323,
		TileOffsets: 324,
		TileByteCounts: 325,
		SubIFDs: 330,
		InkSet: 332,
		InkNames: 333,
		NumberOfInks: 334,
		DotRange: 336,
		TargetPrinter: 337,
		ExtraSamples: 338,
		SampleFormat: 339,
		SMinSampleValue: 340,
		SMaxSampleValue: 341,
		TransferRange: 342,
		ClipPath: 343,
		XClipPathUnits: 344,
		YClipPathUnits: 345,
		Indexed: 346,
		JPEGTables: 347,
		OPIProxy: 351,
		JPEGProc: 512,
		JPEGInterchangeFormat: 513,
		JPEGInterchangeFormatLength: 514,
		JPEGRestartInterval: 515,
		JPEGLosslessPredictors: 517,
		JPEGPointTransforms: 518,
		JPEGQTables: 519,
		JPEGDCTables: 520,
		JPEGACTables: 521,
		YCbCrCoefficients: 529,
		YCbCrSubSampling: 530,
		YCbCrPositioning: 531,
		ReferenceBlackWhite: 532,
		XMLPacket: 700,
		Rating: 18246,
		RatingPercent: 18249,
		ImageID: 32781,
		CFARepeatPatternDim: 33421,
		CFAPattern: 33422,
		BatteryLevel: 33423,
		Copyright: 33432,
		ExposureTime: 33434,
		ImageResources: 34377,
		ExifTag: 34665,
		InterColorProfile: 34675,
		GPSTag: 34853,
		Interlace: 34857,
		TimeZoneOffset: 34858,
		SelfTimerMode: 34859,
		FlashEnergy: 37387,
		SpatialFrequencyResponse: 37388,
		Noise: 37389,
		FocalPlaneXResolution: 37390,
		FocalPlaneYResolution: 37391,
		FocalPlaneResolutionUnit: 37392,
		ImageNumber: 37393,
		SecurityClassification: 37394,
		ImageHistory: 37395,
		ExposureIndex: 37397,
		TIFFEPStandardID: 37398,
		SensingMethod: 37399,
		XPTitle: 40091,
		XPComment: 40092,
		XPAuthor: 40093,
		XPKeywords: 40094,
		XPSubject: 40095,
		PrintImageMatching: 50341,
		DNGVersion: 50706,
		DNGBackwardVersion: 50707,
		UniqueCameraModel: 50708,
		LocalizedCameraModel: 50709,
		CFAPlaneColor: 50710,
		CFALayout: 50711,
		LinearizationTable: 50712,
		BlackLevelRepeatDim: 50713,
		BlackLevel: 50714,
		BlackLevelDeltaH: 50715,
		BlackLevelDeltaV: 50716,
		WhiteLevel: 50717,
		DefaultScale: 50718,
		DefaultCropOrigin: 50719,
		DefaultCropSize: 50720,
		ColorMatrix1: 50721,
		ColorMatrix2: 50722,
		CameraCalibration1: 50723,
		CameraCalibration2: 50724,
		ReductionMatrix1: 50725,
		ReductionMatrix2: 50726,
		AnalogBalance: 50727,
		AsShotNeutral: 50728,
		AsShotWhiteXY: 50729,
		BaselineExposure: 50730,
		BaselineNoise: 50731,
		BaselineSharpness: 50732,
		BayerGreenSplit: 50733,
		LinearResponseLimit: 50734,
		CameraSerialNumber: 50735,
		LensInfo: 50736,
		ChromaBlurRadius: 50737,
		AntiAliasStrength: 50738,
		ShadowScale: 50739,
		DNGPrivateData: 50740,
		MakerNoteSafety: 50741,
		CalibrationIlluminant1: 50778,
		CalibrationIlluminant2: 50779,
		BestQualityScale: 50780,
		RawDataUniqueID: 50781,
		OriginalRawFileName: 50827,
		OriginalRawFileData: 50828,
		ActiveArea: 50829,
		MaskedAreas: 50830,
		AsShotICCProfile: 50831,
		AsShotPreProfileMatrix: 50832,
		CurrentICCProfile: 50833,
		CurrentPreProfileMatrix: 50834,
		ColorimetricReference: 50879,
		CameraCalibrationSignature: 50931,
		ProfileCalibrationSignature: 50932,
		AsShotProfileName: 50934,
		NoiseReductionApplied: 50935,
		ProfileName: 50936,
		ProfileHueSatMapDims: 50937,
		ProfileHueSatMapData1: 50938,
		ProfileHueSatMapData2: 50939,
		ProfileToneCurve: 50940,
		ProfileEmbedPolicy: 50941,
		ProfileCopyright: 50942,
		ForwardMatrix1: 50964,
		ForwardMatrix2: 50965,
		PreviewApplicationName: 50966,
		PreviewApplicationVersion: 50967,
		PreviewSettingsName: 50968,
		PreviewSettingsDigest: 50969,
		PreviewColorSpace: 50970,
		PreviewDateTime: 50971,
		RawImageDigest: 50972,
		OriginalRawFileDigest: 50973,
		SubTileBlockSize: 50974,
		RowInterleaveFactor: 50975,
		ProfileLookTableDims: 50981,
		ProfileLookTableData: 50982,
		OpcodeList1: 51008,
		OpcodeList2: 51009,
		OpcodeList3: 51022,
		NoiseProfile: 51041,
	}
	ExifIFD = {
		ExposureTime: 33434,
		FNumber: 33437,
		ExposureProgram: 34850,
		SpectralSensitivity: 34852,
		ISOSpeedRatings: 34855,
		OECF: 34856,
		SensitivityType: 34864,
		StandardOutputSensitivity: 34865,
		RecommendedExposureIndex: 34866,
		ISOSpeed: 34867,
		ISOSpeedLatitudeyyy: 34868,
		ISOSpeedLatitudezzz: 34869,
		ExifVersion: 36864,
		DateTimeOriginal: 36867,
		DateTimeDigitized: 36868,
		ComponentsConfiguration: 37121,
		CompressedBitsPerPixel: 37122,
		ShutterSpeedValue: 37377,
		ApertureValue: 37378,
		BrightnessValue: 37379,
		ExposureBiasValue: 37380,
		MaxApertureValue: 37381,
		SubjectDistance: 37382,
		MeteringMode: 37383,
		LightSource: 37384,
		Flash: 37385,
		FocalLength: 37386,
		SubjectArea: 37396,
		MakerNote: 37500,
		UserComment: 37510,
		SubSecTime: 37520,
		SubSecTimeOriginal: 37521,
		SubSecTimeDigitized: 37522,
		FlashpixVersion: 40960,
		ColorSpace: 40961,
		PixelXDimension: 40962,
		PixelYDimension: 40963,
		RelatedSoundFile: 40964,
		InteroperabilityTag: 40965,
		FlashEnergy: 41483,
		SpatialFrequencyResponse: 41484,
		FocalPlaneXResolution: 41486,
		FocalPlaneYResolution: 41487,
		FocalPlaneResolutionUnit: 41488,
		SubjectLocation: 41492,
		ExposureIndex: 41493,
		SensingMethod: 41495,
		FileSource: 41728,
		SceneType: 41729,
		CFAPattern: 41730,
		CustomRendered: 41985,
		ExposureMode: 41986,
		WhiteBalance: 41987,
		DigitalZoomRatio: 41988,
		FocalLengthIn35mmFilm: 41989,
		SceneCaptureType: 41990,
		GainControl: 41991,
		Contrast: 41992,
		Saturation: 41993,
		Sharpness: 41994,
		DeviceSettingDescription: 41995,
		SubjectDistanceRange: 41996,
		ImageUniqueID: 42016,
		CameraOwnerName: 42032,
		BodySerialNumber: 42033,
		LensSpecification: 42034,
		LensMake: 42035,
		LensModel: 42036,
		LensSerialNumber: 42037,
		Gamma: 42240,
	}
	GPSIFD = {
		GPSVersionID: 0,
		GPSLatitudeRef: 1,
		GPSLatitude: 2,
		GPSLongitudeRef: 3,
		GPSLongitude: 4,
		GPSAltitudeRef: 5,
		GPSAltitude: 6,
		GPSTimeStamp: 7,
		GPSSatellites: 8,
		GPSStatus: 9,
		GPSMeasureMode: 10,
		GPSDOP: 11,
		GPSSpeedRef: 12,
		GPSSpeed: 13,
		GPSTrackRef: 14,
		GPSTrack: 15,
		GPSImgDirectionRef: 16,
		GPSImgDirection: 17,
		GPSMapDatum: 18,
		GPSDestLatitudeRef: 19,
		GPSDestLatitude: 20,
		GPSDestLongitudeRef: 21,
		GPSDestLongitude: 22,
		GPSDestBearingRef: 23,
		GPSDestBearing: 24,
		GPSDestDistanceRef: 25,
		GPSDestDistance: 26,
		GPSProcessingMethod: 27,
		GPSAreaInformation: 28,
		GPSDateStamp: 29,
		GPSDifferential: 30,
		GPSHPositioningError: 31,
	}
	InteropIFD = {
		InteroperabilityIndex: 1,
	}
	GPSHelper = {
		degToDmsRational(degFloat : number) {
			const degAbs = Math.abs(degFloat);
			const minFloat = degAbs % 1 * 60;
			const secFloat = minFloat % 1 * 60;
			const deg = Math.floor(degAbs);
			const min = Math.floor(minFloat);
			const sec = Math.round(secFloat * 100);

			return [
				[deg, 1],
				[min, 1],
				[sec, 100]
			];
		},
		dmsRationalToDeg(dmsArray : number[][], ref : string) {
			const sign = (ref === 'S' || ref === 'W') ? -1.0 : 1.0;
			const deg = dmsArray[0][0] / dmsArray[0][1] +
				dmsArray[1][0] / dmsArray[1][1] / 60.0 +
				dmsArray[2][0] / dmsArray[2][1] / 3600.0;

			return deg * sign;
		},
	}
	remove(jpeg) {
		let b64 = false;
		if (jpeg.slice(0, 2) == "\xff\xd8") { } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg
			.slice(0, 22) == "data:image/jpg;base64,") {
			jpeg = atob(jpeg.split(",")[1]);
			b64 = true;
		} else {
			throw new Error("Given data is not jpeg.");
		}

		const segments = splitIntoSegments(jpeg);
		const newSegments = segments.filter(function (seg) {
			return !(seg.slice(0, 2) == "\xff\xe1" &&
				seg.slice(4, 10) == "Exif\x00\x00");
		});

		let new_data = newSegments.join("");
		if (b64) {
			new_data = "data:image/jpeg;base64," + btoa(new_data);
		}

		return new_data;
	}
	insert(exif, jpeg) {
		let b64 = false;
		if (exif.slice(0, 6) != "\x45\x78\x69\x66\x00\x00") {
			throw new Error("Given data is not exif.");
		}
		if (jpeg.slice(0, 2) == "\xff\xd8") { } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg
			.slice(0, 22) == "data:image/jpg;base64,") {
			jpeg = atob(jpeg.split(",")[1]);
			b64 = true;
		} else {
			throw new Error("Given data is not jpeg.");
		}

		const exifStr = "\xff\xe1" + pack(">H", [exif.length + 2]) + exif;
		const segments = splitIntoSegments(jpeg);
		let new_data = mergeSegments(segments, exifStr);
		if (b64) {
			new_data = "data:image/jpeg;base64," + btoa(new_data);
		}

		return new_data;
	}
	load(data) {
		let input_data;
		if (isString(data)) {
			if (data.slice(0, 2) == "\xff\xd8") {
				input_data = data;
			} else if (data.slice(0, 23) == "data:image/jpeg;base64," || data.slice(0, 22) == "data:image/jpg;base64,") {
				input_data = atob(data.split(",")[1]);
			} else if (data.slice(0, 4) == "Exif") {
				input_data = data.slice(6);
			} else {
				throw new Error("'load' gots invalid file data.");
			}
		} else {
			throw new Error("'load' gots invalid type argument.");
		}

		let exifDict = {};
		let exif_dict = {
			"0th": {},
			"Exif": {},
			"GPS": {},
			"Interop": {},
			"1st": {},
			"thumbnail": null
		};
		const exifReader = new ExifReader(input_data);
		if (exifReader.tiftag === null) {
			return exif_dict;
		}

		if (exifReader.tiftag.slice(0, 2) == "\x49\x49") {
			exifReader.endian_mark = "<";
		} else {
			exifReader.endian_mark = ">";
		}

		let pointer = unpack(exifReader.endian_mark + "L",
			exifReader.tiftag.slice(4, 8))[0];
		exif_dict["0th"] = exifReader.get_ifd(pointer, "0th");

		const first_ifd_pointer = exif_dict["0th"]["first_ifd_pointer"];
		delete exif_dict["0th"]["first_ifd_pointer"];

		if (34665 in exif_dict["0th"]) {
			pointer = exif_dict["0th"][34665];
			exif_dict["Exif"] = exifReader.get_ifd(pointer, "Exif");
		}
		if (34853 in exif_dict["0th"]) {
			pointer = exif_dict["0th"][34853];
			exif_dict["GPS"] = exifReader.get_ifd(pointer, "GPS");
		}
		if (40965 in exif_dict["Exif"]) {
			pointer = exif_dict["Exif"][40965];
			exif_dict["Interop"] = exifReader.get_ifd(pointer, "Interop");
		}
		if (first_ifd_pointer != "\x00\x00\x00\x00") {
			pointer = unpack(exifReader.endian_mark + "L",
				first_ifd_pointer)[0];
			exif_dict["1st"] = exifReader.get_ifd(pointer, "1st");
			if ((513 in exif_dict["1st"]) && (514 in exif_dict["1st"])) {
				var end = exif_dict["1st"][513] + exif_dict["1st"][514];
				var thumb = exifReader.tiftag.slice(exif_dict["1st"][513], end);
				exif_dict["thumbnail"] = thumb;
			}
		}

		return exif_dict;
	}
	dump(exif_dict_original) {
		const TIFF_HEADER_LENGTH = 8;

		const exif_dict : any = cloneDeep(exif_dict_original);
		const header = "Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08";
		let exif_is = 0//false;
		let gps_is = 0 //false;
		let interop_is = 0//false;
		let first_is = false;

		let zeroth_ifd,
			exif_ifd,
			interop_ifd,
			gps_ifd,
			first_ifd;

		if ("0th" in exif_dict) {
			zeroth_ifd = exif_dict["0th"];
		} else {
			zeroth_ifd = {};
		}

		if ((("Exif" in exif_dict) && (Object.keys(exif_dict["Exif"]).length)) ||
			(("Interop" in exif_dict) && (Object.keys(exif_dict["Interop"]).length))) {
			zeroth_ifd[34665] = 1;
			exif_is = 1//true;
			exif_ifd = exif_dict["Exif"];
			if (("Interop" in exif_dict) && Object.keys(exif_dict["Interop"]).length) {
				exif_ifd[40965] = 1;
				interop_is = 1//true;
				interop_ifd = exif_dict["Interop"];
			} else if (Object.keys(exif_ifd).indexOf(this.ExifIFD.InteroperabilityTag.toString()) > -1) {
				delete exif_ifd[40965];
			}
		} else if (Object.keys(zeroth_ifd).indexOf(this.ImageIFD.ExifTag.toString()) > -1) {
			delete zeroth_ifd[34665];
		}

		if (("GPS" in exif_dict) && (Object.keys(exif_dict["GPS"]).length)) {
			zeroth_ifd[this.ImageIFD.GPSTag] = 1;
			gps_is = 1 //true;
			gps_ifd = exif_dict["GPS"];
		} else if (Object.keys(zeroth_ifd).indexOf(this.ImageIFD.GPSTag.toString()) > -1) {
			delete zeroth_ifd[this.ImageIFD.GPSTag];
		}

		if (("1st" in exif_dict) &&
			("thumbnail" in exif_dict) &&
			(exif_dict["thumbnail"] != null)) {
			first_is = true;
			exif_dict["1st"][513] = 1;
			exif_dict["1st"][514] = 1;
			first_ifd = exif_dict["1st"];
		}

		const zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0);
		const zeroth_length = (zeroth_set[0].length + exif_is * 12 + gps_is * 12 + 4 +
			zeroth_set[1].length);

		let exif_set,
			exif_bytes = "",
			exif_length = 0,
			gps_set,
			gps_bytes = "",
			gps_length = 0,
			interop_set,
			interop_bytes = "",
			interop_length = 0,
			first_set,
			first_bytes = "",
			thumbnail;
		if (exif_is) {
			exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length);
			exif_length = exif_set[0].length + interop_is * 12 + exif_set[1].length;
		}
		if (gps_is) {
			gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length);
			gps_bytes = gps_set.join("");
			gps_length = gps_bytes.length;
		}
		if (interop_is) {
			const offset = zeroth_length + exif_length + gps_length;
			interop_set = _dict_to_bytes(interop_ifd, "Interop", offset);
			interop_bytes = interop_set.join("");
			interop_length = interop_bytes.length;
		}
		if (first_is) {
			const offset = zeroth_length + exif_length + gps_length + interop_length;
			first_set = _dict_to_bytes(first_ifd, "1st", offset);
			thumbnail = _get_thumbnail(exif_dict["thumbnail"]);
			if (thumbnail.length > 64000) {
				throw new Error("Given thumbnail is too large. max 64kB");
			}
		}

		let exif_pointer = "",
			gps_pointer = "",
			interop_pointer = "",
			first_ifd_pointer = "\x00\x00\x00\x00";
		if (exif_is) {
			const pointer_value = TIFF_HEADER_LENGTH + zeroth_length;
			const pointer_str = pack(">L", [pointer_value]);
			const key = 34665;
			const key_str = pack(">H", [key]);
			const type_str = pack(">H", [TYPES["Long"]]);
			const length_str = pack(">L", [1]);
			exif_pointer = key_str + type_str + length_str + pointer_str;
		}
		if (gps_is) {
			const pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length;
			const pointer_str = pack(">L", [pointer_value]);
			const key = 34853;
			const key_str = pack(">H", [key]);
			const type_str = pack(">H", [TYPES["Long"]]);
			const length_str = pack(">L", [1]);
			gps_pointer = key_str + type_str + length_str + pointer_str;
		}
		if (interop_is) {
			const pointer_value = (TIFF_HEADER_LENGTH +
				zeroth_length + exif_length + gps_length);
			const pointer_str = pack(">L", [pointer_value]);
			const key = 40965;
			const key_str = pack(">H", [key]);
			const type_str = pack(">H", [TYPES["Long"]]);
			const length_str = pack(">L", [1]);
			interop_pointer = key_str + type_str + length_str + pointer_str;
		}
		if (first_is) {
			const pointer_value = (TIFF_HEADER_LENGTH + zeroth_length +
				exif_length + gps_length + interop_length);
			first_ifd_pointer = pack(">L", [pointer_value]);
			const thumbnail_pointer = (pointer_value + first_set[0].length + 24 +
				4 + first_set[1].length);
			const thumbnail_p_bytes = ("\x02\x01\x00\x04\x00\x00\x00\x01" +
				pack(">L", [thumbnail_pointer]));
			const thumbnail_length_bytes = ("\x02\x02\x00\x04\x00\x00\x00\x01" +
				pack(">L", [thumbnail.length]));
			first_bytes = (first_set[0] + thumbnail_p_bytes +
				thumbnail_length_bytes + "\x00\x00\x00\x00" +
				first_set[1] + thumbnail);
		}

		const zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer +
			first_ifd_pointer + zeroth_set[1]);
		if (exif_is) {
			exif_bytes = exif_set[0] + interop_pointer + exif_set[1];
		}

		return (header + zeroth_bytes + exif_bytes + gps_bytes +
			interop_bytes + first_bytes);
	}
}



function _get_thumbnail(jpeg) {
	let segments = splitIntoSegments(jpeg);
	while (("\xff\xe0" <= segments[1].slice(0, 2)) && (segments[1].slice(0, 2) <= "\xff\xef")) {
		segments = [segments[0]].concat(segments.slice(2));
	}
	return segments.join("");
}
function _pack_byte(array) {
	return pack(">" + nStr("B", array.length), array);
}

function _pack_short(array) {
	return pack(">" + nStr("H", array.length), array);
}

function _pack_long(array) {
	return pack(">" + nStr("L", array.length), array);
}

function _value_to_bytes(raw_value, value_type, offset) {
	let four_bytes_over = "";
	let value_str = "";
	let length,
		new_value,
		num,
		den;

	if (value_type == "Byte") {
		length = raw_value.length;
		if (length <= 4) {
			value_str = (_pack_byte(raw_value) +
				nStr("\x00", 4 - length));
		} else {
			value_str = pack(">L", [offset]);
			four_bytes_over = _pack_byte(raw_value);
		}
	} else if (value_type == "Short") {
		length = raw_value.length;
		if (length <= 2) {
			value_str = (_pack_short(raw_value) +
				nStr("\x00\x00", 2 - length));
		} else {
			value_str = pack(">L", [offset]);
			four_bytes_over = _pack_short(raw_value);
		}
	} else if (value_type == "Long") {
		length = raw_value.length;
		if (length <= 1) {
			value_str = _pack_long(raw_value);
		} else {
			value_str = pack(">L", [offset]);
			four_bytes_over = _pack_long(raw_value);
		}
	} else if (value_type == "Ascii") {
		new_value = raw_value + "\x00";
		length = new_value.length;
		if (length > 4) {
			value_str = pack(">L", [offset]);
			four_bytes_over = new_value;
		} else {
			value_str = new_value + nStr("\x00", 4 - length);
		}
	} else if (value_type == "Rational") {
		if (typeof (raw_value[0]) == "number") {
			length = 1;
			num = raw_value[0];
			den = raw_value[1];
			new_value = pack(">L", [num]) + pack(">L", [den]);
		} else {
			length = raw_value.length;
			new_value = "";
			for (var n = 0; n < length; n++) {
				num = raw_value[n][0];
				den = raw_value[n][1];
				new_value += (pack(">L", [num]) +
					pack(">L", [den]));
			}
		}
		value_str = pack(">L", [offset]);
		four_bytes_over = new_value;
	} else if (value_type == "SRational") {
		if (typeof (raw_value[0]) == "number") {
			length = 1;
			num = raw_value[0];
			den = raw_value[1];
			new_value = pack(">l", [num]) + pack(">l", [den]);
		} else {
			length = raw_value.length;
			new_value = "";
			for (var n = 0; n < length; n++) {
				num = raw_value[n][0];
				den = raw_value[n][1];
				new_value += (pack(">l", [num]) +
					pack(">l", [den]));
			}
		}
		value_str = pack(">L", [offset]);
		four_bytes_over = new_value;
	} else if (value_type == "Undefined") {
		length = raw_value.length;
		if (length > 4) {
			value_str = pack(">L", [offset]);
			four_bytes_over = raw_value;
		} else {
			value_str = raw_value + nStr("\x00", 4 - length);
		}
	}

	var length_str = pack(">L", [length]);

	return [length_str, value_str, four_bytes_over];
}

function _dict_to_bytes(ifd_dict, ifd, ifd_offset) {
	const TIFF_HEADER_LENGTH = 8;
	const tag_count = Object.keys(ifd_dict).length;
	const entry_header = pack(">H", [tag_count]);
	let entries_length;
	if (["0th", "1st"].indexOf(ifd) > -1) {
		entries_length = 2 + tag_count * 12 + 4;
	} else {
		entries_length = 2 + tag_count * 12;
	}
	let entries = "";
	let values = "";
	let key;

	for (key in ifd_dict) {
		if (typeof (key) == "string") {
			key = parseInt(key);
		}
		if ((ifd == "0th") && ([34665, 34853].indexOf(key) > -1)) {
			continue;
		} else if ((ifd == "Exif") && (key == 40965)) {
			continue;
		} else if ((ifd == "1st") && ([513, 514].indexOf(key) > -1)) {
			continue;
		}

		var raw_value = ifd_dict[key];
		var key_str = pack(">H", [key]);
		var value_type = TAGS[ifd][key]["type"];
		var type_str = pack(">H", [TYPES[value_type]]);

		if (typeof (raw_value) == "number") {
			raw_value = [raw_value];
		}
		var offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + values.length;
		var b = _value_to_bytes(raw_value, value_type, offset);
		var length_str = b[0];
		var value_str = b[1];
		var four_bytes_over = b[2];

		entries += key_str + type_str + length_str + value_str;
		values += four_bytes_over;
	}

	return [entry_header + entries, values];
}



class ExifReader {
	tiftag = null
	endian_mark = ''
	constructor(data) {
		let segments,
			app1;
		if (data.slice(0, 2) == "\xff\xd8") { // JPEG
			segments = splitIntoSegments(data);
			app1 = getExifSeg(segments);
			if (app1) {
				this.tiftag = app1.slice(10);
			} 
		} else if (["\x49\x49", "\x4d\x4d"].indexOf(data.slice(0, 2)) > -1) { // TIFF
			this.tiftag = data;
		} else if (data.slice(0, 4) == "Exif") { // Exif
			this.tiftag = data.slice(6);
		} else {
			throw new Error("Given file is neither JPEG nor TIFF.");
		}
	}
	get_ifd(pointer, ifd_name) {
		let ifd_dict = {};
		let tag_count = unpack(this.endian_mark + "H",
			this.tiftag.slice(pointer, pointer + 2))[0];
		const offset = pointer + 2;
		let t;
		if (["0th", "1st"].indexOf(ifd_name) > -1) {
			t = "Image";
		} else {
			t = ifd_name;
		}

		for (let x = 0; x < tag_count; x++) {
			pointer = offset + 12 * x;
			const tag = unpack(this.endian_mark + "H",
				this.tiftag.slice(pointer, pointer + 2))[0];
			const value_type = unpack(this.endian_mark + "H",
				this.tiftag.slice(pointer + 2, pointer + 4))[0];
			const value_num = unpack(this.endian_mark + "L",
				this.tiftag.slice(pointer + 4, pointer + 8))[0];
			const value = this.tiftag.slice(pointer + 8, pointer + 12);

			const v_set = [value_type, value_num, value];
			if (tag in TAGS[t]) {
				ifd_dict[tag] = this.convert_value(v_set);
			}
		}

		if (ifd_name == "0th") {
			pointer = offset + 12 * tag_count;
			ifd_dict["first_ifd_pointer"] = this.tiftag.slice(pointer, pointer + 4);
		}

		return ifd_dict;
	}

	convert_value(val) {
		let data = null;
		const t = val[0];
		const length = val[1];
		const value = val[2];
		let pointer;

		if (t == 1) { // BYTE
			if (length > 4) {
				pointer = unpack(this.endian_mark + "L", value)[0];
				data = unpack(this.endian_mark + nStr("B", length),
					this.tiftag.slice(pointer, pointer + length));
			} else {
				data = unpack(this.endian_mark + nStr("B", length), value.slice(0, length));
			}
		} else if (t == 2) { // ASCII
			if (length > 4) {
				pointer = unpack(this.endian_mark + "L", value)[0];
				data = this.tiftag.slice(pointer, pointer + length - 1);
			} else {
				data = value.slice(0, length - 1);
			}
		} else if (t == 3) { // SHORT
			if (length > 2) {
				pointer = unpack(this.endian_mark + "L", value)[0];
				data = unpack(this.endian_mark + nStr("H", length),
					this.tiftag.slice(pointer, pointer + length * 2));
			} else {
				data = unpack(this.endian_mark + nStr("H", length),
					value.slice(0, length * 2));
			}
		} else if (t == 4) { // LONG
			if (length > 1) {
				pointer = unpack(this.endian_mark + "L", value)[0];
				data = unpack(this.endian_mark + nStr("L", length),
					this.tiftag.slice(pointer, pointer + length * 4));
			} else {
				data = unpack(this.endian_mark + nStr("L", length),
					value);
			}
		} else if (t == 5) { // RATIONAL
			pointer = unpack(this.endian_mark + "L", value)[0];
			if (length > 1) {
				data = [];
				for (let x = 0; x < length; x++) {
					data.push([unpack(this.endian_mark + "L",
						this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0],
					unpack(this.endian_mark + "L",
						this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0]
					]);
				}
			} else {
				data = [unpack(this.endian_mark + "L",
					this.tiftag.slice(pointer, pointer + 4))[0],
				unpack(this.endian_mark + "L",
					this.tiftag.slice(pointer + 4, pointer + 8))[0]
				];
			}
		} else if (t == 7) { // UNDEFINED BYTES
			if (length > 4) {
				pointer = unpack(this.endian_mark + "L", value)[0];
				data = this.tiftag.slice(pointer, pointer + length);
			} else {
				data = value.slice(0, length);
			}
		} else if (t == 9) { // SLONG
			if (length > 1) {
				pointer = unpack(this.endian_mark + "L", value)[0];
				data = unpack(this.endian_mark + nStr("l", length),
					this.tiftag.slice(pointer, pointer + length * 4));
			} else {
				data = unpack(this.endian_mark + nStr("l", length),
					value);
			}
		} else if (t == 10) { // SRATIONAL
			pointer = unpack(this.endian_mark + "L", value)[0];
			if (length > 1) {
				data = [];
				for (let x = 0; x < length; x++) {
					data.push([unpack(this.endian_mark + "l",
						this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0],
					unpack(this.endian_mark + "l",
						this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0]
					]);
				}
			} else {
				data = [unpack(this.endian_mark + "l",
					this.tiftag.slice(pointer, pointer + 4))[0],
				unpack(this.endian_mark + "l",
					this.tiftag.slice(pointer + 4, pointer + 8))[0]
				];
			}
		} else {
			throw new Error("Exif might be wrong. Got incorrect value " +
				"type to decode. type:" + t);
		}

		if ((data instanceof Array) && (data.length == 1)) {
			return data[0];
		} else {
			return data;
		}
	}

}


function pack(mark, array) {
	if (!(array instanceof Array)) {
		throw new Error("'pack' error. Got invalid type argument.");
	}
	if ((mark.length - 1) != array.length) {
		throw new Error("'pack' error. " + (mark.length - 1) + " marks, " + array.length + " elements.");
	}

	let littleEndian;
	if (mark[0] == "<") {
		littleEndian = true;
	} else if (mark[0] == ">") {
		littleEndian = false;
	} else {
		throw new Error("");
	}
	let packed = "";
	let p = 1;
	let val = null;
	let c = null;
	let valStr = null;

	while (c = mark[p]) {
		if (c.toLowerCase() == "b") {
			val = array[p - 1];
			if ((c == "b") && (val < 0)) {
				val += 0x100;
			}
			if ((val > 0xff) || (val < 0)) {
				throw new Error("'pack' error.");
			} else {
				valStr = String.fromCharCode(val);
			}
		} else if (c == "H") {
			val = array[p - 1];
			if ((val > 0xffff) || (val < 0)) {
				throw new Error("'pack' error.");
			} else {
				valStr = String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) +
					String.fromCharCode(val % 0x100);
				if (littleEndian) {
					valStr = valStr.split("").reverse().join("");
				}
			}
		} else if (c.toLowerCase() == "l") {
			val = array[p - 1];
			if ((c == "l") && (val < 0)) {
				val += 0x100000000;
			}
			if ((val > 0xffffffff) || (val < 0)) {
				throw new Error("'pack' error.");
			} else {
				valStr = String.fromCharCode(Math.floor(val / 0x1000000)) +
					String.fromCharCode(Math.floor((val % 0x1000000) / 0x10000)) +
					String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) +
					String.fromCharCode(val % 0x100);
				if (littleEndian) {
					valStr = valStr.split("").reverse().join("");
				}
			}
		} else {
			throw new Error("'pack' error.");
		}

		packed += valStr;
		p += 1;
	}

	return packed;
}


function unpack(mark, str) {
	if (typeof (str) != "string") {
		throw new Error("'unpack' error. Got invalid type argument.");
	}
	let l = 0;
	for (let markPointer = 1; markPointer < mark.length; markPointer++) {
		if (mark[markPointer].toLowerCase() == "b") {
			l += 1;
		} else if (mark[markPointer].toLowerCase() == "h") {
			l += 2;
		} else if (mark[markPointer].toLowerCase() == "l") {
			l += 4;
		} else {
			throw new Error("'unpack' error. Got invalid mark.");
		}
	}

	if (l != str.length) {
		throw new Error("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length);
	}

	let littleEndian;
	if (mark[0] == "<") {
		littleEndian = true;
	} else if (mark[0] == ">") {
		littleEndian = false;
	} else {
		throw new Error("'unpack' error.");
	}
	let unpacked = [];
	let strPointer = 0;
	let p = 1;
	let val = null;
	let c = null;
	let length = null;
	let sliced = "";

	while (c = mark[p]) {
		if (c.toLowerCase() == "b") {
			length = 1;
			sliced = str.slice(strPointer, strPointer + length);
			val = sliced.charCodeAt(0);
			if ((c == "b") && (val >= 0x80)) {
				val -= 0x100;
			}
		} else if (c == "H") {
			length = 2;
			sliced = str.slice(strPointer, strPointer + length);
			if (littleEndian) {
				sliced = sliced.split("").reverse().join("");
			}
			val = sliced.charCodeAt(0) * 0x100 +
				sliced.charCodeAt(1);
		} else if (c.toLowerCase() == "l") {
			length = 4;
			sliced = str.slice(strPointer, strPointer + length);
			if (littleEndian) {
				sliced = sliced.split("").reverse().join("");
			}
			val = sliced.charCodeAt(0) * 0x1000000 +
				sliced.charCodeAt(1) * 0x10000 +
				sliced.charCodeAt(2) * 0x100 +
				sliced.charCodeAt(3);
			if ((c == "l") && (val >= 0x80000000)) {
				val -= 0x100000000;
			}
		} else {
			throw new Error("'unpack' error. " + c);
		}

		unpacked.push(val);
		strPointer += length;
		p += 1;
	}

	return unpacked;
}


function nStr(ch, num) {
	let str = "";
	for (let i = 0; i < num; i++) {
		str += ch;
	}
	return str;
}


function splitIntoSegments(data) {
	if (data.slice(0, 2) != "\xff\xd8") {
		throw new Error("Given data isn't JPEG.");
	}

	let head = 2;
	const segments = ["\xff\xd8"];
	while (true) {
		if (data.slice(head, head + 2) == "\xff\xda") {
			segments.push(data.slice(head));
			break;
		} else {
			var length = unpack(">H", data.slice(head + 2, head + 4))[0];
			var endPoint = head + length + 2;
			segments.push(data.slice(head, endPoint));
			head = endPoint;
		}

		if (head >= data.length) {
			throw new Error("Wrong JPEG data.");
		}
	}
	return segments;
}


function getExifSeg(segments) {
	let seg;
	for (let i = 0; i < segments.length; i++) {
		seg = segments[i];
		if (seg.slice(0, 2) == "\xff\xe1" &&
			seg.slice(4, 10) == "Exif\x00\x00") {
			return seg;
		}
	}
	return null;
}


function mergeSegments(segments, exif) {
	let hasExifSegment = false;
	let additionalAPP1ExifSegments = [];

	segments.forEach(function (segment, i) {
		// Replace first occurence of APP1:Exif segment
		if (segment.slice(0, 2) == "\xff\xe1" &&
			segment.slice(4, 10) == "Exif\x00\x00"
		) {
			if (!hasExifSegment) {
				segments[i] = exif;
				hasExifSegment = true;
			} else {
				additionalAPP1ExifSegments.unshift(i);
			}
		}
	});

	// Remove additional occurences of APP1:Exif segment
	additionalAPP1ExifSegments.forEach(function (segmentIndex) {
		segments.splice(segmentIndex, 1);
	});

	if (!hasExifSegment && exif) {
		segments = [segments[0], exif].concat(segments.slice(1));
	}

	return segments.join("");
}


